雖然昨天說了很多應該用interface,不建議用抽象類別的原因,但interface有個缺點:無法重複使用程式碼,如果想要有interface可mixins的優點,又要有抽象類別可以讓子類別重複使用程式碼的優勢,可以提供abstract skeletal implementation。
skeletal implementation也被稱為AbstractInterface,collection架構有很多這種設計,像是AbstractCollection
和AbstractSet
...,使用上非常方便,只要實作沒有被實作的抽象方法,就能夠使用了。
import java.util.AbstractList;
import java.util.List;
public class ConvertType {
static List<Integer> intArrayAsList(final int[] a) {
if (a == null) {
throw new NullPointerException();
}
return new AbstractList<Integer>() {
@Override
public Integer get(int index) {
return a[index];
}
@Override
public int size() {
return a.length;
}
};
}
public static void main(String[] args) {
int[] myArray = {1, 2, 3};
List<Integer> myList = intArrayAsList(myArray);
System.out.println(myList);
}
}
使用簡單,要自行設計skeletal implementation也不難,只要設計一個抽象類別,並實作interface,就完成skeletal implementation,流程上會像下面這張圖,中間的抽象類別其實就像Day 21: 最好使用composition而不是繼承(下)介紹過的forwarding class。
使用的時候,可以參考上面AbstractList
的範例,以匿名類別(Anonymous class)的方式使用,也可以參考下面範例,新增一個類別,在用繼承抽象類別的方式來使用,當然,不管用哪一種方式,都是離不開繼承,所以設計skeletal implementation時,務必要遵守Day 22: 設計並記錄繼承的使用方式,否則禁止繼承的原則。
// 定義一個接口
interface Animal {
void eat();
void sleep();
}
// 定義一個抽象類實現該接口
abstract class Mammal implements Animal {
// 提供接口方法的一部分實現
@Override
public void sleep() {
System.out.println("Mammal is sleeping");
}
// 抽象方法,留給子類實現
@Override
public abstract void eat();
}
// 具體子類實現剩餘的抽象方法
public class Dog extends Mammal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 輸出: Dog is eating
dog.sleep(); // 輸出: Mammal is sleeping
}
}
skeletal implementation除了可以結合interface和抽象類別的優點之外,還有另外一個優點是,如果要在既有的interface加方法,或需要實作新的interface,可以在抽象類別做調整,不會影響繼承抽象類別的類別。但如果類別是自己實作interface,不是透過抽象類別做使用,interface幾乎不可能加方法,因為這個會造成很多實作該interface的類別,無法compile成功。
說了這麼多skeletal implementation的優點,最後還是要特別提醒,避免濫用skeletal implementation,因為當越來越多類別繼承抽象類別,哪天要改寫時,會發現影響的範圍很大,終究還是回到Day 20: 最好使用composition而不是繼承(上)說的那些老問題。